package com.tdk.mybatisplus.demo.common.util;

import static com.baomidou.mybatisplus.generator.config.ConstVal.SERVICE_IMPL;
import static com.tdk.mybatisplus.demo.common.entity.Constants.BATCH_INSERT_ON_DUPLICATE_KEY_IGNORE;
import static com.tdk.mybatisplus.demo.common.entity.Constants.BATCH_INSERT_ON_DUPLICATE_KEY_UPDATE;
import static com.tdk.mybatisplus.demo.common.entity.Constants.ISO_TIME_PATTERN_WITH_MILLI_SECOND;
import static com.tdk.mybatisplus.demo.common.entity.Constants.ISO_TIME_PATTERN_WITH_NANO_SECOND;
import static com.tdk.mybatisplus.demo.common.entity.Constants.LIST_OBJS;
import static com.tdk.mybatisplus.demo.common.entity.Constants.ONE;
import static com.tdk.mybatisplus.demo.common.entity.Constants.SAVE_BATCH;
import static com.tdk.mybatisplus.demo.common.entity.Constants.TIME_PATTERN_WITH_NANO_SECOND;
import static com.tdk.mybatisplus.demo.common.entity.Constants.TIME_PATTERN_WITH_WITH_MILLI_SECOND;
import static com.tdk.mybatisplus.demo.common.entity.Constants.UPDATE_BATCH_BY_ID;
import static com.tdk.mybatisplus.demo.common.entity.Constants.ZERO;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

/**
 * <p>
 * MybatisPlusBatchUpdateOrInsertUtil
 * </p>
 *
 * @author: taodingkai
 * @modified:
 * @since: 2021/9/10 14:44
 */
@Slf4j
@Component
public class MybatisPlusBatchUpdateOrInsertUtil {

  public static final String EXCEPTION_MESSAGE = "执行异常 ";
  @Autowired
  private ApplicationContext applicationContext;
  
  /**
   * <p>
   * 存储数据到数据库 比较慢
   * </p>
   *
   * @param description
   * @param entityList
   * @param dbEntityClass
   * @author: taodingkai
   * @since: 2021/9/6 16:40
   */
  public void store2Db(
      String description, List<? extends Object> entityList,
      Class<? extends Object> dbEntityClass) {
    try {
      // 获取数据库里的id列表
      Object myBatisPlusService = applicationContext.getBean(
          dbEntityClass.getSimpleName().substring(Math.toIntExact(ZERO), Math.toIntExact(ONE)).toLowerCase()
              + dbEntityClass.getSimpleName().substring(Math.toIntExact(ONE)) + SERVICE_IMPL);
      Method listObjsMethod = myBatisPlusService.getClass().getMethod(LIST_OBJS, Wrapper.class);
      listObjsMethod.setAccessible(true);
      //查询数据库里的id
      List<Object> ids = (List<Object>) listObjsMethod
          .invoke(myBatisPlusService, getLambdaQueryWrapper(dbEntityClass));
      //分成insert和update
      List<Object> insertList = entityList.parallelStream().filter(entity -> {
        try {
          return !ids.contains(getPrimaryKeyField(dbEntityClass).get(entity));
        } catch (Exception e) {
          log.error(description + EXCEPTION_MESSAGE, e);
        }
        return false;
      }).collect(Collectors.toList());

      List<Object> updateList = entityList.parallelStream().filter(entity -> {
        try {
          return ids.contains(getPrimaryKeyField(dbEntityClass).get(entity));
        } catch (Exception e) {
          log.error(description + EXCEPTION_MESSAGE, e);
        }
        return false;
      }).collect(Collectors.toList());
      //入库
      Method saveBatchMethod = myBatisPlusService.getClass()
          .getMethod(SAVE_BATCH, Collection.class);
      saveBatchMethod.setAccessible(true);
      saveBatchMethod.invoke(myBatisPlusService, insertList);
      Method updateBatchByIdMethod = myBatisPlusService.getClass()
          .getMethod(UPDATE_BATCH_BY_ID, Collection.class);
      updateBatchByIdMethod.setAccessible(true);
      updateBatchByIdMethod.invoke(myBatisPlusService, updateList);
      log.debug(description + "({})数据,共同步{}条数据", dbEntityClass.getSimpleName(), entityList.size());
    } catch (Exception e) {
      if (e instanceof IllegalArgumentException) {
        log.error(description + "({})执行异常: ", dbEntityClass.getSimpleName(), e);
      } else {
        log.error(description + EXCEPTION_MESSAGE, e);
      }

    }
  }

  /**
   * <p>
   * batchInsertOnDuplicateKeyUpdate 需要配合myMapper.xml.ftl模板
   * </p>
   *
   *
   * @param description
   * @param entityList
   * @param dbEntityClass
   * @author: taodingkai
   * @since: 2021/11/18 15:07 
   */
  public void batchInsertOnDuplicateKeyUpdate(String description, List<? extends Object> entityList,Class<? extends Object> dbEntityClass) {
    try {
      // 获取service
      Object myBatisPlusService = applicationContext.getBean(
          dbEntityClass.getSimpleName().substring(Math.toIntExact(ZERO), Math.toIntExact(ONE)).toLowerCase()
              + dbEntityClass.getSimpleName().substring(Math.toIntExact(ONE)) + SERVICE_IMPL);
      // 入库
      Method batchInsertOnDuplicateKeyUpdateMethod = myBatisPlusService.getClass()
          .getMethod(BATCH_INSERT_ON_DUPLICATE_KEY_UPDATE, Collection.class);
      batchInsertOnDuplicateKeyUpdateMethod.setAccessible(true);
      batchInsertOnDuplicateKeyUpdateMethod.invoke(myBatisPlusService, entityList);

      log.debug(description + "({})数据,{}共同步{}条数据", dbEntityClass.getSimpleName(),BATCH_INSERT_ON_DUPLICATE_KEY_UPDATE, entityList.size());
    } catch (Exception e) {
      if (e instanceof IllegalArgumentException) {
        log.error(description + "({}) {} 执行异常: ", dbEntityClass.getSimpleName(),BATCH_INSERT_ON_DUPLICATE_KEY_UPDATE, e);
      } else {
        log.error(description + BATCH_INSERT_ON_DUPLICATE_KEY_UPDATE, e);
      }
    }
  }

  /**
   * <p>
   * batchInsertOnDuplicateKeyIgnore 需要配合myMapper.xml.ftl模板
   * </p>
   *
   *
   * @param description
   * @param entityList
   * @param dbEntityClass
   * @author: taodingkai
   * @since: 2021/11/18 15:07 
   */
  public void batchInsertOnDuplicateKeyIgnore(String description, List<? extends Object> entityList,Class<? extends Object> dbEntityClass) {
    try {
      // 获取service
      Object myBatisPlusService = applicationContext.getBean(
          dbEntityClass.getSimpleName().substring(Math.toIntExact(ZERO), Math.toIntExact(ONE)).toLowerCase()
              + dbEntityClass.getSimpleName().substring(Math.toIntExact(ONE)) + SERVICE_IMPL);
      // 入库
      Method batchInsertOnDuplicateKeyIgnoreMethod = myBatisPlusService.getClass()
          .getMethod(BATCH_INSERT_ON_DUPLICATE_KEY_IGNORE, Collection.class);
      batchInsertOnDuplicateKeyIgnoreMethod.setAccessible(true);
      batchInsertOnDuplicateKeyIgnoreMethod.invoke(myBatisPlusService, entityList);

      log.debug(description + "({})数据,{}共同步{}条数据", dbEntityClass.getSimpleName(),BATCH_INSERT_ON_DUPLICATE_KEY_IGNORE, entityList.size());
    } catch (Exception e) {
      if (e instanceof IllegalArgumentException) {
        log.error(description + "({}) {} 执行异常: ", dbEntityClass.getSimpleName(),BATCH_INSERT_ON_DUPLICATE_KEY_IGNORE, e);
      } else {
        log.error(description + BATCH_INSERT_ON_DUPLICATE_KEY_IGNORE, e);
      }
    }
  }

  /**
   * <p>
   * 获取主键列表查询QueryWrapper
   * </p>
   *
   * @param tClass
   * @return com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<T>
   * @author: taodingkai
   * @since: 2021/7/28 17:47
   */
  public <T> QueryWrapper<T> getLambdaQueryWrapper(Class<T> tClass) {
    Field primaryKeyField = getPrimaryKeyField(tClass);
    return Wrappers.<T>query().select(primaryKeyField.getAnnotation(TableId.class).value());
  }

  /**
   * <p>
   * 获取主键字段
   * </p>
   *
   * @param tClass
   * @return java.lang.reflect.Field
   * @author: taodingkai
   * @since: 2021/7/28 17:47
   */
  public <T> Field getPrimaryKeyField(Class<T> tClass) {
    Field   primaryKeyField = null;
    Field[] fields          = tClass.getDeclaredFields();
    for (int j = 0; j < fields.length; j++) {
      Field field = fields[j];
      field.setAccessible(true);
      if (field.isAnnotationPresent(TableId.class)) {
        primaryKeyField = field;
        break;
      }
    }
    return primaryKeyField;
  }
}
